import os
import uuid
import shutil
import traceback
import hashlib
from pathlib import Path
from typing import Tuple, List, Optional
from fastapi import UploadFile, HTTPException
from starlette import status

from app.modules.files.models import FileModel
from app.modules.users.models import UserModel
from app.core.exceptions import BusinessException, NotFoundException
from app.modules.system.services import SystemConfigService

# 定义上传文件的根目录和基础URL
UPLOAD_DIR = Path("uploads")
BASE_URL = "/static/uploads"


async def _calculate_file_hash(file: UploadFile) -> str:
    """
    计算文件的MD5哈希值
    支持大文件，分块读取
    """
    hash_md5 = hashlib.md5()
    # 重置文件指针到开头，以防之前被读取过
    await file.seek(0)
    for chunk in iter(lambda: file.file.read(4096), b""):
        hash_md5.update(chunk)
    # 再次重置文件指针，以便后续操作（如保存）可以从头读取
    await file.seek(0)
    return hash_md5.hexdigest()


class FileService:
    """
    文件服务，处理文件的上传、下载、删除等操作
    """

    @staticmethod
    async def upload_file(
        file: UploadFile,
        current_user: UserModel,
        sub_dir: str = ""
    ) -> FileModel:
        """
        处理文件上传，增加哈希校验和秒传功能
        :param file: FastAPI的UploadFile对象
        :param current_user: 当前上传用户
        :param sub_dir: 文件存储的子目录，例如 'avatars', 'images'
        :return: FileModel实例
        """
        try:
            print("--- 开始文件上传处理 ---")

            # --- 1. 验证文件 ---
            # 此处省略了从数据库动态获取配置的详细代码，以便聚焦核心逻辑
            # 实际应用中应保留 SystemConfigService 的调用
            max_size = 10 * 1024 * 1024  # 10MB
            allowed_extensions = ["jpg", "jpeg", "png", "pdf", "gif"]
            
            file_extension = file.filename.split(".")[-1].lower()
            if file_extension not in allowed_extensions:
                raise BusinessException(message=f"不支持的文件类型: {file_extension}")
            
            # 获取并验证文件大小
            size = await file.read()
            if len(size) > max_size:
                raise BusinessException(message="文件大小超过限制")
            await file.seek(0) # 读完后指针归零

            # --- 2. 计算文件哈希值并检查是否存在 ---
            print("\n步骤 2: 计算哈希并检查文件是否已存在...")
            file_hash = await _calculate_file_hash(file)
            print(f"文件哈希 (MD5): {file_hash}")
            
            existing_file = await FileModel.get_or_none(hash=file_hash)
            if existing_file:
                print("文件已存在 (秒传)，直接返回现有文件信息。")
                # 尽管文件已存在，但仍关闭当前上传的文件流
                file.file.close()
                return existing_file

            # --- 3. 生成存储路径和文件名 (如果文件不存在) ---
            print("\n步骤 3: 生成存储信息...")
            saved_filename = f"{uuid.uuid4()}.{file_extension}"
            save_path = UPLOAD_DIR / sub_dir
            save_path.mkdir(parents=True, exist_ok=True)
            file_path_on_disk = save_path / saved_filename
            print(f"文件将保存至: {file_path_on_disk}")

            # --- 4. 保存文件到磁盘 ---
            print("\n步骤 4: 写入文件到磁盘...")
            try:
                with open(file_path_on_disk, "wb") as buffer:
                    shutil.copyfileobj(file.file, buffer)
                print("文件写入成功。")
            finally:
                file.file.close()

            # --- 5. 创建数据库记录 ---
            print("\n步骤 5: 创建数据库记录...")
            file_url = f"{BASE_URL}/{sub_dir}/{saved_filename}".replace("//", "/")
            
            file_record = await FileModel.create(
                filename=file.filename,
                saved_filename=saved_filename,
                path=str(save_path),
                url=file_url,
                size=len(size),
                hash=file_hash, # 保存哈希值
                mime_type=file.content_type,
                uploader=current_user
            )
            print("数据库记录创建成功。")
            print("--- 文件上传处理完毕 ---")
            return file_record

        except Exception as e:
            print("\n--- !!! 文件上传过程中发生异常 !!! ---")
            print(traceback.format_exc())
            print(f"异常类型: {type(e).__name__}")
            print(f"异常信息: {e}")
            print("--------------------------------------")
            # 重新抛出异常，以便FastAPI的异常处理器能够捕获
            raise

    @staticmethod
    async def update_file(
        file_id: int,
        file: UploadFile,
        current_user: UserModel,
        sub_dir: str = ""
    ) -> FileModel:
        """
        更新已有文件。
        - 上传新文件
        - 计算新文件哈希
        - 更新数据库记录
        - 删除旧的物理文件
        :param file_id: 要更新的文件的ID
        :param file: 新的UploadFile对象
        :param current_user: 当前操作用户
        :param sub_dir: 文件存储的子目录
        :return: 更新后的FileModel实例
        """
        print(f"--- 开始更新文件 (ID: {file_id}) ---")
        
        # --- 1. 查找旧文件记录 ---
        old_file_record = await FileService.get_file_by_id(file_id)
        old_file_path_on_disk = Path(old_file_record.path) / old_file_record.saved_filename
        print(f"找到旧文件记录，路径: {old_file_path_on_disk}")

        # --- 2. 处理新文件（与上传逻辑类似） ---
        # 验证、计算哈希、保存
        max_size = 10 * 1024 * 1024  # 10MB
        allowed_extensions = ["jpg", "jpeg", "png", "pdf", "gif"]
        file_extension = file.filename.split(".")[-1].lower()
        if file_extension not in allowed_extensions:
            raise BusinessException(message=f"不支持的文件类型: {file_extension}")
        
        new_size_bytes = await file.read()
        if len(new_size_bytes) > max_size:
            raise BusinessException(message="文件大小超过限制")
        await file.seek(0)

        new_file_hash = await _calculate_file_hash(file)
        print(f"新文件哈希 (MD5): {new_file_hash}")

        # 检查新文件是否与系统中另一个已存在文件内容相同 (非当前更新的文件)
        existing_file_with_same_hash = await FileModel.get_or_none(hash=new_file_hash)
        if existing_file_with_same_hash and existing_file_with_same_hash.id != file_id:
             raise BusinessException(message="更新失败：与系统中另一个已存在的文件内容重复。")


        saved_filename = f"{uuid.uuid4()}.{file_extension}"
        save_path = UPLOAD_DIR / sub_dir
        save_path.mkdir(parents=True, exist_ok=True)
        new_file_path_on_disk = save_path / saved_filename

        try:
            with open(new_file_path_on_disk, "wb") as buffer:
                shutil.copyfileobj(file.file, buffer)
            print(f"新文件成功保存至: {new_file_path_on_disk}")
        finally:
            file.file.close()

        # --- 3. 更新数据库记录 ---
        file_url = f"{BASE_URL}/{sub_dir}/{saved_filename}".replace("//", "/")
        
        old_file_record.filename = file.filename
        old_file_record.saved_filename = saved_filename
        old_file_record.path = str(save_path)
        old_file_record.url = file_url
        old_file_record.size = len(new_size_bytes)
        old_file_record.hash = new_file_hash
        old_file_record.mime_type = file.content_type
        # uploader 不变，因为是同一个人更新

        await old_file_record.save()
        print("数据库记录更新成功。")

        # --- 4. 删除旧的物理文件 ---
        if old_file_path_on_disk.exists() and old_file_path_on_disk.is_file():
            os.remove(old_file_path_on_disk)
            print(f"旧的物理文件已删除: {old_file_path_on_disk}")
        else:
            print(f"[警告] 未找到或无法删除旧文件: {old_file_path_on_disk}")

        print("--- 文件更新处理完毕 ---")
        return old_file_record

    @staticmethod
    async def get_file_list(
        skip: int = 0,
        limit: int = 10,
        uploader_id: Optional[int] = None,
        filename: Optional[str] = None
    ) -> Tuple[int, List[FileModel]]:
        """
        获取文件列表（带分页和筛选）
        """
        query = FileModel.all()
        if uploader_id:
            query = query.filter(uploader_id=uploader_id)
        if filename:
            query = query.filter(filename__icontains=filename)
        
        total = await query.count()
        files = await query.offset(skip).limit(limit).select_related("uploader")
        
        return total, files

    @staticmethod
    async def get_file_by_id(file_id: int) -> FileModel:
        """
        根据ID获取文件信息
        """
        file = await FileModel.get_or_none(id=file_id)
        if not file:
            raise NotFoundException(message="文件未找到")
        return file

    @staticmethod
    async def delete_file(file_id: int) -> None:
        """
        删除文件（包括物理文件和数据库记录）
        """
        file_record = await FileService.get_file_by_id(file_id)
        
        # --- 1. 从磁盘删除物理文件 ---
        file_path_on_disk = Path(file_record.path) / file_record.saved_filename
        if file_path_on_disk.exists():
            os.remove(file_path_on_disk)
            
        # --- 2. 从数据库删除记录 ---
        await file_record.delete() 